package com.exercise.widgets.floatingbutton.dragfloatingactionbutton

import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.ViewConfiguration
import android.view.animation.DecelerateInterpolator
import android.widget.FrameLayout
import com.google.android.material.floatingactionbutton.FloatingActionButton
import kotlin.math.abs
import kotlin.math.roundToInt

/**
 * 可拖拽自动左右吸边的FloatingActionButton基类.
 *  可以在子类中配置吸边时的边距等
 */
abstract class BaseDragFloatingActionButton(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : FloatingActionButton(context, attrs, defStyleAttr) {
    private var mTouchSlop = 0
    private val mToolBarHeight = dp2px(56F)

    private var mDownX = 0F
    private var mDownY = 0F
    private var mViewWidth = 0
    private var mViewHeight = 0
    private var mDragRatio = 0.5

    private var isMove = false
    private var mOnClickListener: OnClickListener? = null

    /**
     * 吸左时的左边局,可在子类中重写,默认为0
     */
    protected open fun getDefaultLeftMargin(): Int {
        return 0
    }

    /**
     * 吸右时的右边距,可在子类中重写,默认为0
     */
    protected open fun getDefaultRightMargin(): Int {
        return 0
    }

    /**
     * 吸顶时的上边距,可在子类中重写,默认为ToolBar高度
     */
    protected open fun getDefaultTopMargin(): Int {
        return mToolBarHeight
    }

    /**
     * 吸底时的下边距,可在子类中重写,默认为 0
     */
    protected open fun getDefaultBottomMargin(): Int {
        return 0
    }

    init {
        initView(context)
    }

    private fun initView(context: Context) {
        mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
        post {
            mViewWidth = this.width
            mViewHeight = this.height
        }
    }

    /**
     * 设置点击事件
     */
    override fun setOnClickListener(l: OnClickListener?) {
        this.mOnClickListener = l
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(ev: MotionEvent): Boolean {
        val x = ev.x
        val y = ev.y
        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                mDownX = ev.x
                mDownY = ev.y
            }

            MotionEvent.ACTION_MOVE -> {
                val diffY = abs(y - mDownY)
                val diffX = abs(x - mDownX)
                if (diffX > mTouchSlop || diffY > mTouchSlop) {
                    //拖动距离超过阈值mTouchSlop,则视为拖拽
                    isMove = true
                    offsetTopAndBottom((y - mDownY).roundToInt())
                    offsetLeftAndRight((x - mDownX).roundToInt())
                }
            }

            MotionEvent.ACTION_UP -> {
                if (isMove) {
                    //拖拽,水平方向吸边
                    adsorbHorizontal(ev)
                } else {
                    //非拖拽,处理点击事件
                    mOnClickListener?.onClick(this)
                }
                isMove = false
            }
        }
        return true
    }

    /**
     * 水平方向吸边
     */
    private fun adsorbHorizontal(event: MotionEvent) {
        /**
         * 1.判断滑动距离是否超过半屏
         * 2.判断其实位置在左/右半屏
         * 3.左半屏
         *      3.1 滑动距离>半屏=吸右
         *      3.2 滑动距离<半屏=吸左
         * 4.右半屏
         *      4.1 滑动距离>半屏=吸左
         *      4.2 滑动距离<半屏=吸右
         */
        if (isOriginalFromLeft()) {
            //左半屏
            val centerX = abs(event.rawX - mDownX) + mViewWidth / 2
            if (centerX < getAdsorbWidth()) {
                //滑动距离<半屏=吸左
                adsorbToLeft()
            } else {
                //滑动距离>半屏=吸右
                adsorbToRight()
            }
        } else {
            //右半屏
            val centerX = abs(event.rawX - mDownX) + mViewWidth / 2
            if (centerX < getAdsorbWidth()) {
                //滑动距离<半屏=吸右
                adsorbToRight()
            } else {
                //滑动距离>半屏=吸左
                adsorbToLeft()
            }
        }
        resetVertical(event)
    }

    /**
     * 水平方向拖拽时,如果垂直方向拖拽距离超出屏幕,则水平吸边时上下也吸边
     */
    private fun resetVertical(ev: MotionEvent) {
        if (ev.rawY < mViewHeight) {
            adsorbToTop()
        } else if (ev.rawY > getContentHeight() - mViewHeight) {
            adsorbToBottom()
        }
    }

    /**
     * 吸左
     */
    private fun adsorbToLeft() {
        val leftX = getDefaultLeftMargin()
        animate().setInterpolator(DecelerateInterpolator()).setDuration(300)
            .x(leftX.toFloat()).start()
    }

    /**
     * 吸右
     */
    private fun adsorbToRight() {
        val rightX = getScreenWidth() - mViewWidth - getDefaultRightMargin()
        animate().setInterpolator(DecelerateInterpolator()).setDuration(300)
            .x(rightX.toFloat()).start()
    }

    /**
     * 吸顶
     *
     */
    private fun adsorbToTop() {
        val topY = 0F + getDefaultTopMargin()
        animate()
            .setInterpolator(DecelerateInterpolator())
            .setDuration(300)
            .y(topY)
            .start()
    }

    /**
     * 吸底
     */
    private fun adsorbToBottom() {
        val bottomY = getContentHeight() - mViewHeight - getDefaultBottomMargin()
        animate()
            .setInterpolator(DecelerateInterpolator())
            .setDuration(300)
            .y(bottomY.toFloat())
            .start()
    }

    /**
     * 初始位置是否在左半屏
     */
    private fun isOriginalFromLeft(): Boolean {
        return mDownX < getScreenWidth() / 2
    }

    /**
     * 获取左右吸边时需要拖拽的距离
     */
    private fun getAdsorbWidth(): Double {
        return getScreenWidth() * mDragRatio
    }

    protected fun dp2px(dp: Float): Int {
        val density = context.resources.displayMetrics.density
        return (density * dp + 0.5f).toInt()
    }

    /**
     * 获取内容区域的高度
     */
    private fun getContentHeight(): Int {
        val contentView =
            (context as? Activity)?.window?.decorView?.findViewById<FrameLayout>(android.R.id.content)
        return contentView?.bottom ?: 0
    }

    /**
     * 获取屏幕宽度
     */
    private fun getScreenWidth(): Int {
        val dm = context.resources.displayMetrics
        return dm.widthPixels
    }
}